﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing.Drawing2D;
using System.Globalization;

namespace System.Drawing.Tests;

public class PenTests
{
    public static IEnumerable<object[]> Ctor_Brush_TestData()
    {
        yield return new object[] { new SolidBrush(Color.Red), PenType.SolidColor };
        yield return new object[] { new HatchBrush(HatchStyle.BackwardDiagonal, Color.Red), PenType.HatchFill };
        yield return new object[] { new LinearGradientBrush(new Point(0, 0), new Point(0, 2), Color.Purple, Color.Plum), PenType.LinearGradient };
        yield return new object[] { new TextureBrush(new Bitmap(1, 1)), PenType.TextureFill };
        yield return new object[] { new PathGradientBrush([new(1, 2), new(2, 3)]), PenType.PathGradient };
    }

    [Theory]
    [MemberData(nameof(Ctor_Brush_TestData))]
    public void Ctor_Brush<T>(T brush, PenType penType) where T : Brush
    {
        using (brush)
        using (Pen pen = new(brush))
        {
            VerifyPen<T>(pen, penType, expectedWidth: 1);
        }
    }

    public static IEnumerable<object[]> Ctor_Brush_Width_TestData()
    {
        foreach (object[] data in Ctor_Brush_TestData())
        {
            yield return new object[] { data[0], 10, data[1] };
        }

        // Issue on Non-English Windows with brush width < 1. See https://github.com/dotnet/runtime/issues/60480
        if (Environment.OSVersion.Platform != PlatformID.Win32NT || CultureInfo.InstalledUICulture.TwoLetterISOLanguageName == "en")
        {
            yield return new object[] { new SolidBrush(Color.Red), 0, PenType.SolidColor };

            // GDI+ No longer allows negative pen width values (Windows PR6441324)
            // yield return new object[] { new SolidBrush(Color.Red), -1, PenType.SolidColor };
            // yield return new object[] { new SolidBrush(Color.Red), float.NegativeInfinity, PenType.SolidColor };
        }

        yield return new object[] { new SolidBrush(Color.Red), float.PositiveInfinity, PenType.SolidColor };
        yield return new object[] { new SolidBrush(Color.Red), float.NaN, PenType.SolidColor };
        yield return new object[] { new SolidBrush(Color.Red), float.MaxValue, PenType.SolidColor };
    }

    [Theory]
    [MemberData(nameof(Ctor_Brush_Width_TestData))]
    public void Ctor_Brush_Width<T>(T brush, float width, PenType expectedPenType) where T : Brush
    {
        using (brush)
        using (Pen pen = new(brush, width))
        {
            VerifyPen<T>(pen, expectedPenType, width);
        }
    }

    [Fact]
    public void Ctor_Brush_MakesClone()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        brush.Color = Color.Blue;
        Assert.Equal(Color.FromArgb(Color.Red.ToArgb()), pen.Color);
        Assert.Equal(pen.Color, Assert.IsType<SolidBrush>(pen.Brush).Color);
    }

    [Fact]
    public void Ctor_NullBrush_ThrowsArgumentNullException()
    {
        AssertExtensions.Throws<ArgumentNullException>("brush", () => new Pen(null));
        AssertExtensions.Throws<ArgumentNullException>("brush", () => new Pen(null, 0));
    }

    [Fact]
    public void Ctor_DisposedBrush_ThrowsArgumentException()
    {
        SolidBrush brush = new(Color.Red);
        brush.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => new Pen(brush));
        AssertExtensions.Throws<ArgumentException>(null, () => new Pen(brush, 10));
    }

    [Fact]
    public void Ctor_Color()
    {
        using Pen pen = new(Color.Red);
        VerifyPen<SolidBrush>(pen, PenType.SolidColor, 1);
        Assert.Equal(Color.Red, pen.Color);
    }

    [Theory]
    // GDI+ No longer allows negative pen width values (Windows PR6441324)
    // [InlineData(-1)]
    // [InlineData(float.NegativeInfinity)]
    [InlineData(0)]
    [InlineData(1)]
    [InlineData(float.PositiveInfinity)]
    [InlineData(float.NaN)]
    public void Ctor_Color_Width(float width)
    {
        using Pen pen = new(Color.Red, width);
        VerifyPen<SolidBrush>(pen, PenType.SolidColor, width);
        Assert.Equal(Color.Red, pen.Color);
    }

    [Theory]
    [InlineData(PenAlignment.Center)]
    [InlineData(PenAlignment.Inset)]
    [InlineData(PenAlignment.Left)]
    [InlineData(PenAlignment.Outset)]
    [InlineData(PenAlignment.Right)]
    public void Alignment_SetValid_GetReturnsExpected(PenAlignment alignment)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.Alignment = alignment;
        Assert.Equal(alignment, pen.Alignment);
    }

    [Theory]
    [InlineData(PenAlignment.Center - 1)]
    [InlineData(PenAlignment.Right + 1)]
    public void Alignment_SetInvalid_ThrowsInvalidEnumArgumentException(PenAlignment alignment)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.ThrowsAny<ArgumentException>(() => pen.Alignment = alignment);
    }

    [Fact]
    public void Alignment_GetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.Alignment);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Alignment = PenAlignment.Center);
    }

    [Theory]
    [MemberData(nameof(Ctor_Brush_TestData))]
    public void Brush_SetValid_GetReturnsExpected<T>(T brush, PenType penType) where T : Brush
    {
        using Pen pen = new(Color.Red);
        pen.Brush = brush;
        Assert.IsType<T>(pen.Brush);
        Assert.Equal(penType, pen.PenType);
    }

    [Fact]
    public void Brush_SetCustomBrush_ThrowsArgumentException()
    {
        using Pen pen = new(Color.Red);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Brush = new SubBrush());
    }

    public class SubBrush : Brush
    {
        public override object Clone() => this;
    }

    [Fact]
    public void Brush_SetNullBrush_ThrowsArgumentNullException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentNullException>("value", () => pen.Brush = null);
    }

    [Fact]
    public void Brush_SetDisposedBrush_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        brush.Dispose();
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Brush = brush);
    }

    [Fact]
    public void Brush_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.Brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Brush = brush);
    }

    public static IEnumerable<object[]> Clone_TestData()
    {
        using SolidBrush brush = new(Color.Red);
        yield return new object[] { new Pen(brush) };
    }

    [Theory]
    [MemberData(nameof(Clone_TestData))]
    public void Clone_Invoke_ReturnsExpected(Pen pen)
    {
        using (pen)
        {
            Pen clone = Assert.IsType<Pen>(pen.Clone());
            Assert.NotSame(pen, clone);

            Assert.Equal(pen.Color, clone.Color);
            Assert.Equal(((SolidBrush)pen.Brush).Color, ((SolidBrush)clone.Brush).Color);
            Assert.Equal(pen.DashOffset, clone.DashOffset);
            Assert.Equal(pen.DashStyle, clone.DashStyle);
            Assert.Equal(pen.EndCap, clone.EndCap);
            Assert.Equal(pen.StartCap, clone.StartCap);
            Assert.Equal(pen.Width, clone.Width);
        }
    }

    [Fact]
    public void Clone_Disposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, pen.Clone);
    }

    [Fact]
    public void Color_SolidBrush_ReturnsExpected()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.Equal(Color.FromArgb(Color.Red.ToArgb()), pen.Color);
    }

    [Fact]
    public void Color_HatchBrush_ThrowsArgumentException()
    {
        using HatchBrush brush = new(HatchStyle.BackwardDiagonal, Color.Red);
        using Pen pen = new(brush);
        ValidateInitialPenColorState(pen);
    }

    [Fact]
    public void Color_LinearGradientBrush_ThrowsArgumentException()
    {
        using LinearGradientBrush brush = new(Point.Empty, new Point(1, 2), Color.Blue, Color.Red);
        using Pen pen = new(brush);
        ValidateInitialPenColorState(pen);
    }

    private static void ValidateInitialPenColorState(Pen pen)
    {
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Color);
    }

    [Theory]
    [MemberData(nameof(Ctor_Brush_TestData))]
    public void Color_Set_GetReturnsExpected(Brush brush, PenType penType)
    {
        _ = penType;
        using (brush)
        using (Pen pen = new(brush))
        {
            pen.Color = Color.Red;
            Assert.Equal(Color.Red, pen.Color);

            pen.Color = Color.Red;
            Assert.Equal(Color.Red, pen.Color);
        }
    }

    [Fact]
    public void Color_GetSetWhenDisposedWithoutBrush_Success()
    {
        Pen pen = new(Color.Red);

        pen.Dispose();
        Assert.Equal(Color.Red, pen.Color);

        pen.Color = Color.Red;
        Assert.Equal(Color.Red, pen.Color);

        AssertExtensions.Throws<ArgumentException>(null, () => pen.Color = Color.Black);
    }

    [Fact]
    public void Color_GetSetWhenDisposedWithBrush_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.Color);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Color = Color.Red);
    }

    [Theory]
    [InlineData(new float[] { 0, 0 })]
    [InlineData(new float[] { 1, 1 })]
    [InlineData(new float[] { float.NaN, 0 })]
    public void CompoundArray_SetValidPattern_GetReturnsExpected(float[] compoundArray)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.CompoundArray = compoundArray;
        Assert.Equal(compoundArray, pen.CompoundArray);

        // CompoundArray should be a clone of the original.
        compoundArray[0] = 10;
        Assert.NotEqual(compoundArray, pen.CompoundArray);
    }

    [Fact]
    public void CompoundArray_SetNullPattern_ThrowsArgumentNullException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.Throws<ArgumentNullException>(() => pen.CompoundArray = null);
    }

    [Fact]
    public void CompoundArray_SetEmptyPattern_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CompoundArray = []);
    }

    [Theory]
    [InlineData(new float[] { -1, 0 })]
    [InlineData(new float[] { float.NegativeInfinity, 0 })]
    [InlineData(new float[] { float.PositiveInfinity, 0 })]
    [InlineData(new float[] { float.MaxValue, 0 })]
    [InlineData(new float[] { 1024, 0 })]
    [InlineData(new float[] { 2, 2 })]
    [InlineData(new float[] { 1 })]
    [InlineData(new float[] { 1, 2, 3 })]
    public void CompoundArray_SetInvalidPattern_ThrowsArgumentException(float[] compoundArray)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CompoundArray = compoundArray);
    }

    [Fact]
    public void CompoundArray_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.CompoundArray);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CompoundArray = [1]);
    }

    [Fact]
    public void CustomEndCap_SetValid_GetReturnsExpected()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using GraphicsPath fillPath = new();
        using GraphicsPath strokePath = new();
        using CustomLineCap lineCap = new(fillPath, strokePath);
        lineCap.BaseInset = 100;
        pen.CustomEndCap = lineCap;
        Assert.Equal(100, pen.CustomEndCap.BaseInset);

        // The CustomLineCap should be cloned.
        lineCap.BaseInset = 10;
        Assert.Equal(100, pen.CustomEndCap.BaseInset);
    }

    [Fact]
    public void CustomEndCap_SetNull_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomEndCap = null);
    }

    [Fact]
    public void CustomEndCap_SetDisposedLineCap_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using GraphicsPath fillPath = new();
        using GraphicsPath strokePath = new();
        CustomLineCap lineCap = new(fillPath, strokePath);
        lineCap.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomEndCap = lineCap);
    }

    [Fact]
    public void CustomEndCap_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using GraphicsPath fillPath = new();
        using GraphicsPath strokePath = new();
        using CustomLineCap lineCap = new(fillPath, strokePath);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomEndCap);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomEndCap = lineCap);
    }

    [Fact]
    public void CustomStartCap_SetValid_GetReturnsExpected()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using GraphicsPath fillPath = new();
        using GraphicsPath strokePath = new();
        using CustomLineCap lineCap = new(fillPath, strokePath);
        lineCap.BaseInset = 100;
        pen.CustomStartCap = lineCap;
        Assert.Equal(100, pen.CustomStartCap.BaseInset);

        // The CustomLineCap should be cloned.
        lineCap.BaseInset = 10;
        Assert.Equal(100, pen.CustomStartCap.BaseInset);
    }

    [Fact]
    public void CustomStartCap_SetNull_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomStartCap = null);
    }

    [Fact]
    public void CustomStartCap_SetDisposedLineCap_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using GraphicsPath fillPath = new();
        using GraphicsPath strokePath = new();
        CustomLineCap lineCap = new(fillPath, strokePath);
        lineCap.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomStartCap = lineCap);
    }

    [Fact]
    public void CustomStartCap_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using GraphicsPath fillPath = new();
        using GraphicsPath strokePath = new();
        using CustomLineCap lineCap = new(fillPath, strokePath);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomStartCap);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomStartCap = lineCap);
    }

    [Theory]
    [InlineData(DashCap.Flat)]
    [InlineData(DashCap.Round)]
    [InlineData(DashCap.Triangle)]
    public void DashCap_SetValid_GetReturnsExpected(DashCap dashCap)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.DashCap = dashCap;
        Assert.Equal(dashCap, pen.DashCap);
    }

    [Theory]
    [InlineData(DashCap.Flat - 1)]
    [InlineData(DashCap.Round - 1)]
    [InlineData(DashCap.Triangle + 1)]
    public void DashCap_SetInvalid_ThrowsInvalidEnumArgumentException(DashCap dashCap)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.ThrowsAny<ArgumentException>(() => pen.DashCap = dashCap);
    }

    [Fact]
    public void DashCap_GetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashCap);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashCap = DashCap.Triangle);
    }

    [Theory]
    [InlineData(-1)]
    [InlineData(0)]
    [InlineData(10)]
    [InlineData(float.NegativeInfinity)]
    [InlineData(float.PositiveInfinity)]
    [InlineData(float.NaN)]
    public void DashOffset_Set_GetReturnsExpected(float dashOffset)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.DashOffset = dashOffset;
        Assert.Equal(dashOffset, pen.DashOffset);
    }

    [Fact]
    public void DashOffset_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashOffset);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashOffset = 10);
    }

    [Theory]
    [InlineData(new float[] { 1 })]
    [InlineData(new float[] { 1, 1 })]
    [InlineData(new float[] { float.MaxValue, float.NaN, float.PositiveInfinity })]
    [InlineData(new float[] { float.MaxValue, float.NaN })]
    public void DashPattern_SetValidPattern_GetReturnsExpected(float[] pattern)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.DashPattern = pattern;
        Assert.Equal(pattern, pen.DashPattern);
        Assert.Equal(DashStyle.Custom, pen.DashStyle);

        // DashPattern should be a clone of the original.
        pattern[0] = 10;
        Assert.NotEqual(pattern, pen.DashPattern);
    }

    [Fact]
    public void DashPattern_SetNullPattern_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashPattern = null);
    }

    [Fact]
    public void DashPattern_SetEmptyPattern_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashPattern = []);
    }

    [Theory]
    [InlineData(new float[] { -1 })]
    [InlineData(new float[] { 0 })]
    [InlineData(new float[] { 1, -1 })]
    [InlineData(new float[] { float.NegativeInfinity })]
    public void DashPattern_SetInvalidPattern_ThrowsArgumentException(float[] pattern)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashPattern = pattern);
    }

    [Fact]
    public void DashPattern_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashPattern);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashPattern = [1]);
    }

    [Theory]
    [InlineData(DashStyle.Dash, new float[] { 3, 1 })]
    [InlineData(DashStyle.DashDot, new float[] { 3, 1, 1, 1 })]
    [InlineData(DashStyle.DashDotDot, new float[] { 3, 1, 1, 1, 1, 1 })]
    [InlineData(DashStyle.Dot, new float[] { 1, 1 })]
    [InlineData(DashStyle.Solid, null)]
    [InlineData(DashStyle.Custom, new float[] { 1 })]
    public void DashStyle_SetValid_GetReturnsExpected(DashStyle dashStyle, float[]? expectedDashPattern)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.DashStyle = dashStyle;
        Assert.Equal(dashStyle, pen.DashStyle);

        if (expectedDashPattern is null)
        {
            Assert.Throws<InvalidOperationException>(() => pen.DashPattern);
        }
        else
        {
            Assert.Equal(expectedDashPattern, pen.DashPattern);
        }
    }

    [Fact]
    public void DashStyle_SetCustomWithDashCount_DoeNotChangePattern()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.DashStyle = DashStyle.Dash;
        pen.DashStyle = DashStyle.Custom;

        Assert.Equal(DashStyle.Custom, pen.DashStyle);
        Assert.Equal([3, 1], pen.DashPattern);
    }

    [Theory]
    [InlineData(DashStyle.Solid - 1)]
    [InlineData(DashStyle.Custom + 1)]
    public void DashStyle_SetInvalid_ThrowsInvalidEnumArgumentException(DashStyle dashStyle)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.ThrowsAny<ArgumentException>(() => pen.DashStyle = dashStyle);
    }

    [Fact]
    public void DashStyle_GetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashStyle);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.DashStyle = DashStyle.Dash);
    }

    public static IEnumerable<object[]> LineCap_Valid_TestData()
    {
        yield return new object[] { LineCap.Flat };
        yield return new object[] { LineCap.Square };
        yield return new object[] { LineCap.Round };
        yield return new object[] { LineCap.Triangle };
        yield return new object[] { LineCap.NoAnchor };
        yield return new object[] { LineCap.SquareAnchor };
        yield return new object[] { LineCap.RoundAnchor };
        yield return new object[] { LineCap.DiamondAnchor };
        yield return new object[] { LineCap.ArrowAnchor };
        yield return new object[] { LineCap.AnchorMask };
        yield return new object[] { LineCap.Custom };
    }

    [Theory]
    [MemberData(nameof(LineCap_Valid_TestData))]
    public void EndCap_SetValid_GetReturnsExpected(LineCap lineCap)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.EndCap = lineCap;
        Assert.Equal(lineCap, pen.EndCap);
    }

    public static IEnumerable<object[]> LineCap_Invalid_TestData()
    {
        yield return new object[] { LineCap.Flat - 1 };
        yield return new object[] { LineCap.Triangle + 1 };
        yield return new object[] { LineCap.ArrowAnchor + 1 };
        yield return new object[] { LineCap.AnchorMask + 1 };
        yield return new object[] { LineCap.Custom + 1 };
    }

    [Theory]
    [MemberData(nameof(LineCap_Invalid_TestData))]
    public void EndCap_SetInvalid_ThrowsInvalidEnumArgumentException(LineCap lineCap)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.ThrowsAny<ArgumentException>(() => pen.EndCap = lineCap);
    }

    [Fact]
    public void EndCap_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.EndCap);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.EndCap = LineCap.ArrowAnchor);
    }

    [Theory]
    [InlineData(LineJoin.Bevel)]
    [InlineData(LineJoin.Miter)]
    [InlineData(LineJoin.MiterClipped)]
    [InlineData(LineJoin.Round)]
    public void LineJoin_SetValid_GetReturnsExpected(LineJoin lineJoin)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.LineJoin = lineJoin;
        Assert.Equal(lineJoin, pen.LineJoin);
    }

    [Theory]
    [InlineData(LineJoin.Miter - 1)]
    [InlineData(LineJoin.MiterClipped + 1)]
    public void LineJoin_SetInvalid_ThrowsInvalidEnumArgumentException(LineJoin lineJoin)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.ThrowsAny<ArgumentException>(() => pen.LineJoin = lineJoin);
    }

    [Fact]
    public void LineJoin_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.LineJoin);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.LineJoin = LineJoin.Miter);
    }

    [Theory]
    [InlineData(-1, 1)]
    [InlineData(0, 1)]
    [InlineData(10, 10)]
    [InlineData(float.NegativeInfinity, 1)]
    [InlineData(float.PositiveInfinity, float.PositiveInfinity)]
    [InlineData(float.NaN, float.NaN)]
    public void MiterLimit_Set_GetReturnsExpected(float miterLimit, float expectedMiterLimit)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.MiterLimit = miterLimit;
        Assert.Equal(expectedMiterLimit, pen.MiterLimit);
    }

    [Fact]
    public void MiterLimit_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.MiterLimit);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.MiterLimit = 10);
    }

    public static IEnumerable<object[]> MultiplyTransform_TestData()
    {
        yield return new object[] { new Matrix(), new Matrix(1, 2, 3, 4, 5, 6), MatrixOrder.Prepend };
        yield return new object[] { new Matrix(), new Matrix(1, 2, 3, 4, 5, 6), MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), new Matrix(2, 3, 4, 5, 6, 7), MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), new Matrix(2, 3, 4, 5, 6, 7), MatrixOrder.Append };
    }

    [Theory]
    [MemberData(nameof(MultiplyTransform_TestData))]
    public void MultiplyTransform_Matrix_SetsTransformToExpected(Matrix originalTransform, Matrix matrix, MatrixOrder matrixOrder)
    {
        try
        {
            using SolidBrush brush = new(Color.Red);
            using Pen pen = new(brush);
            using Matrix expected = originalTransform.Clone();
            expected.Multiply(matrix, matrixOrder);
            pen.Transform = originalTransform;

            if (matrixOrder == MatrixOrder.Prepend)
            {
                Pen clone = (Pen)pen.Clone();
                clone.MultiplyTransform(matrix);
                Assert.Equal(expected, clone.Transform);
            }

            pen.MultiplyTransform(matrix, matrixOrder);
            Assert.Equal(expected, pen.Transform);
        }
        finally
        {
            originalTransform.Dispose();
            matrix.Dispose();
        }
    }

    [Fact]
    public void MultiplyTransform_NullMatrix_ThrowsArgumentNullException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.Throws<ArgumentNullException>(() => pen.MultiplyTransform(null));
        Assert.Throws<ArgumentNullException>(() => pen.MultiplyTransform(null, MatrixOrder.Prepend));
    }

    [Fact]
    public void MultiplyTransform_NotInvertibleMatrix_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using Matrix matrix = new(123, 24, 82, 16, 47, 30);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.MultiplyTransform(matrix));
        AssertExtensions.Throws<ArgumentException>(null, () => pen.MultiplyTransform(matrix, MatrixOrder.Prepend));
    }

    [Fact]
    public void MultiplyTransform_DisposedMatrix_Nop()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using Matrix transform = new(1, 2, 3, 4, 5, 6);
        pen.Transform = transform;

        Matrix matrix = new();
        matrix.Dispose();

        pen.MultiplyTransform(matrix);
        pen.MultiplyTransform(matrix, MatrixOrder.Append);

        Assert.Equal(transform, pen.Transform);
    }

    [Theory]
    [InlineData(MatrixOrder.Prepend - 1)]
    [InlineData(MatrixOrder.Append + 1)]
    public void MultiplyTransform_InvalidOrder_Nop(MatrixOrder matrixOrder)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using Matrix transform = new(1, 2, 3, 4, 5, 6);
        using Matrix matrix = new();
        pen.Transform = transform;

        pen.MultiplyTransform(matrix, matrixOrder);
        Assert.Equal(transform, pen.Transform);
    }

    [Fact]
    public void MultiplyTransform_Disposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Matrix matrix = new();
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.MultiplyTransform(matrix));
        AssertExtensions.Throws<ArgumentException>(null, () => pen.MultiplyTransform(matrix, MatrixOrder.Prepend));
    }

    [Fact]
    public void ResetTransform_Invoke_SetsTransformToZero()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using Matrix transform = new(1, 2, 3, 4, 5, 6);
        using Matrix matrix = new();
        pen.Transform = transform;
        pen.ResetTransform();
        Assert.Equal(matrix, pen.Transform);

        pen.ResetTransform();
        Assert.Equal(matrix, pen.Transform);
    }

    [Fact]
    public void ResetTransform_Disposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Matrix matrix = new();
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, pen.ResetTransform);
    }

    public static IEnumerable<object[]> RotateTransform_TestData()
    {
        yield return new object[] { new Matrix(), 90, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(), 90, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 360, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 360, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), -45, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), -45, MatrixOrder.Append };
    }

    [Theory]
    [MemberData(nameof(RotateTransform_TestData))]
    public void RotateTransform_Invoke_SetsTransformToExpected(Matrix originalTransform, float angle, MatrixOrder matrixOrder)
    {
        using (originalTransform)
        using (SolidBrush brush = new(Color.Red))
        using (Pen pen = new(brush))
        using (Matrix expected = originalTransform.Clone())
        {
            expected.Rotate(angle, matrixOrder);
            pen.Transform = originalTransform;

            if (matrixOrder == MatrixOrder.Prepend)
            {
                Pen clone = (Pen)pen.Clone();
                clone.RotateTransform(angle);
                Assert.Equal(expected, clone.Transform);
            }

            pen.RotateTransform(angle, matrixOrder);
            Assert.Equal(expected, pen.Transform);
        }
    }

    [Theory]
    [InlineData(MatrixOrder.Prepend - 1)]
    [InlineData(MatrixOrder.Append + 1)]
    public void RotateTransform_InvalidOrder_ThrowsArgumentException(MatrixOrder matrixOrder)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.RotateTransform(10, matrixOrder));
    }

    [Fact]
    public void RotateTransform_Disposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Matrix matrix = new();
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.RotateTransform(1));
        AssertExtensions.Throws<ArgumentException>(null, () => pen.RotateTransform(1, MatrixOrder.Prepend));
    }

    public static IEnumerable<object[]> ScaleTransform_TestData()
    {
        yield return new object[] { new Matrix(), 2, 3, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(), 2, 3, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0, 0, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0, 0, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 1, 1, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 1, 1, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), -2, -3, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), -2, -3, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0.5, 0.75, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0.5, 0.75, MatrixOrder.Append };
    }

    [Theory]
    [MemberData(nameof(ScaleTransform_TestData))]
    public void ScaleTransform_Invoke_SetsTransformToExpected(Matrix originalTransform, float scaleX, float scaleY, MatrixOrder matrixOrder)
    {
        using (originalTransform)
        using (SolidBrush brush = new(Color.Red))
        using (Pen pen = new(brush))
        using (Matrix expected = originalTransform.Clone())
        {
            expected.Scale(scaleX, scaleY, matrixOrder);
            pen.Transform = originalTransform;

            if (matrixOrder == MatrixOrder.Prepend)
            {
                Pen clone = (Pen)pen.Clone();
                clone.ScaleTransform(scaleX, scaleY);
                Assert.Equal(expected, clone.Transform);
            }

            pen.ScaleTransform(scaleX, scaleY, matrixOrder);
            Assert.Equal(expected, pen.Transform);
        }
    }

    [Theory]
    [InlineData(MatrixOrder.Prepend - 1)]
    [InlineData(MatrixOrder.Append + 1)]
    public void ScaleTransform_InvalidOrder_ThrowsArgumentException(MatrixOrder matrixOrder)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.ScaleTransform(1, 2, matrixOrder));
    }

    [Fact]
    public void ScaleTransform_Disposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Matrix matrix = new();
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.ScaleTransform(1, 2));
        AssertExtensions.Throws<ArgumentException>(null, () => pen.ScaleTransform(1, 2, MatrixOrder.Prepend));
    }

    [Theory]
    [InlineData(LineCap.Flat, LineCap.Round, DashCap.Triangle)]
    [InlineData(LineCap.Flat - 1, LineCap.Flat - 1, DashCap.Flat - 1)]
    [InlineData((LineCap)int.MaxValue, (LineCap)int.MaxValue, (DashCap)int.MaxValue)]
    public void SetLineCap_Invoke_Success(LineCap startCap, LineCap endCap, DashCap dashCap)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        // Make sure that if DashCap is invalid then it is reset to Flat.
        if (Enum.IsDefined(typeof(DashCap), dashCap))
        {
            pen.DashCap = DashCap.Round;
        }

        pen.SetLineCap(startCap, endCap, dashCap);
        Assert.Equal(startCap, pen.StartCap);
        Assert.Equal(endCap, pen.EndCap);
        Assert.Equal(Enum.IsDefined(typeof(DashCap), dashCap) ? dashCap : DashCap.Flat, pen.DashCap);
    }

    [Fact]
    public void SetLineCap_Disposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.SetLineCap(LineCap.AnchorMask, LineCap.ArrowAnchor, DashCap.Flat));
    }

    [Theory]
    [MemberData(nameof(LineCap_Valid_TestData))]
    public void StartCap_SetValid_GetReturnsExpected(LineCap lineCap)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.StartCap = lineCap;
        Assert.Equal(lineCap, pen.StartCap);
    }

    [Theory]
    [MemberData(nameof(LineCap_Invalid_TestData))]
    public void StartCap_SetInvalid_ThrowsInvalidEnumArgumentException(LineCap lineCap)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Assert.ThrowsAny<ArgumentException>(() => pen.StartCap = lineCap);
    }

    [Fact]
    public void StartCap_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.StartCap);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.StartCap = LineCap.ArrowAnchor);
    }

    [Fact]
    public void Transform_SetValid_GetReturnsExpected()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using Matrix matrix = new(1, 2, 3, 4, 5, 6);
        using Matrix expected = new(1, 2, 3, 4, 5, 6);
        pen.Transform = matrix;
        Assert.Equal(matrix, pen.Transform);

        // The Matrix should be cloned.
        matrix.Translate(1, 2);
        Assert.Equal(expected, pen.Transform);
    }

    [Fact]
    public void Transform_SetNull_ThrowsArgumentNullException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentNullException>("value", () => pen.Transform = null);
    }

    [Fact]
    public void Transform_SetNotInvertible_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        using Matrix matrix = new(123, 24, 82, 16, 47, 30);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Transform = matrix);
    }

    [Fact]
    public void Transform_SetDisposedLineCap_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        Matrix matrix = new();
        matrix.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.Transform = matrix);
    }

    [Fact]
    public void Transform_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Matrix matrix = new();
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.Transform);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Transform = matrix);
    }

    public static IEnumerable<object[]> TranslateTransform_TestData()
    {
        yield return new object[] { new Matrix(), 2, 3, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(), 2, 3, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0, 0, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0, 0, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 1, 1, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 1, 1, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), -2, -3, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), -2, -3, MatrixOrder.Append };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0.5, 0.75, MatrixOrder.Prepend };
        yield return new object[] { new Matrix(1, 2, 3, 4, 5, 6), 0.5, 0.75, MatrixOrder.Append };
    }

    [Theory]
    [MemberData(nameof(TranslateTransform_TestData))]
    public void TranslateTransform_Invoke_SetsTransformToExpected(Matrix originalTransform, float dX, float dY, MatrixOrder matrixOrder)
    {
        using (originalTransform)
        using (SolidBrush brush = new(Color.Red))
        using (Pen pen = new(brush))
        using (Matrix expected = originalTransform.Clone())
        {
            expected.Translate(dX, dY, matrixOrder);
            pen.Transform = originalTransform;

            if (matrixOrder == MatrixOrder.Prepend)
            {
                Pen clone = (Pen)pen.Clone();
                clone.TranslateTransform(dX, dY);
                Assert.Equal(expected, clone.Transform);
            }

            pen.TranslateTransform(dX, dY, matrixOrder);
            Assert.Equal(expected, pen.Transform);
        }
    }

    [Theory]
    [InlineData(MatrixOrder.Prepend - 1)]
    [InlineData(MatrixOrder.Append + 1)]
    public void TranslateTransform_InvalidOrder_ThrowsArgumentException(MatrixOrder matrixOrder)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.TranslateTransform(1, 2, matrixOrder));
    }

    [Fact]
    public void TranslateTransform_Disposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        using Matrix matrix = new();
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.TranslateTransform(1, 2));
        AssertExtensions.Throws<ArgumentException>(null, () => pen.TranslateTransform(1, 2, MatrixOrder.Prepend));
    }

    [Theory]
    [InlineData(-10)]
    [InlineData(0)]
    [InlineData(10)]
    [InlineData(float.NegativeInfinity)]
    [InlineData(float.PositiveInfinity)]
    [InlineData(float.NaN)]
    public void Width_Set_GetReturnsExpected(float value)
    {
        using SolidBrush brush = new(Color.Red);
        using Pen pen = new(brush);
        pen.Width = value;
        Assert.Equal(value, pen.Width);
    }

    [Fact]
    public void Width_GetSetWhenDisposed_ThrowsArgumentException()
    {
        using SolidBrush brush = new(Color.Red);
        Pen pen = new(brush);
        pen.Dispose();

        AssertExtensions.Throws<ArgumentException>(null, () => pen.Width);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.Width = 10);
    }

    private static void VerifyPen<T>(Pen pen, PenType expectedPenType, float expectedWidth) where T : Brush
    {
        Assert.Equal(PenAlignment.Center, pen.Alignment);

        Assert.IsType<T>(pen.Brush);

        Assert.Empty(pen.CompoundArray);

        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomEndCap);
        AssertExtensions.Throws<ArgumentException>(null, () => pen.CustomStartCap);

        Assert.Equal(DashCap.Flat, pen.DashCap);
        Assert.Equal(0, pen.DashOffset);

        Assert.Throws<InvalidOperationException>(() => pen.DashPattern);

        Assert.Equal(DashStyle.Solid, pen.DashStyle);
        Assert.Equal(LineCap.Flat, pen.EndCap);
        Assert.Equal(LineJoin.Miter, pen.LineJoin);
        Assert.Equal(10, pen.MiterLimit);
        Assert.Equal(expectedPenType, pen.PenType);
        Assert.Equal(LineCap.Flat, pen.StartCap);

        using (Matrix matrix = new())
        {
            Assert.Equal(new Matrix(), pen.Transform);
        }

        Assert.Equal(expectedWidth, pen.Width);
    }
}
